﻿namespace com.crownpack.export
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Text;

    using com.crownpack.ExportConfigurationDao;
    using com.crownpack.Extensions.DateTimeExtensions;
    using com.crownpack.Extensions.SharePointExtensions;
    using com.crownpack.Extensions.StreamExtensions;
    using com.crownpack.SharePoint;

    using Microsoft.SharePoint;

    public class ExportManager
    {
        #region Fields

        const string AUTHOR = "Author";
        const char COLUMNDELIMITER = '\t'; // tab
        const string CONTENTTYPEID = "ContentTypeId";
        const string CREATED = "Created";
        const string EDITOR = "Editor";
        const string ITEMURL = "ItemUrl";
        const string MODIFIED = "Modified";
        const string UNIQUEID = "UniqueId";
        const char VALUEDELIMITER = '\v'; // \b backspace

        List<string> _BaseFields;

        #endregion Fields

        #region Enumerations

        public enum HeaderConstant
        {
            InternalName,
            None,
            Title
        }

        #endregion Enumerations

        #region Properties

        internal List<ExportConfiguration> Configuration
        {
            get;
            set;
        }

        internal string CurrentWebUrl
        {
            get;
            set;
        }

        List<string> BaseFields
        {
            get
            {
                if ( _BaseFields == null )
                {
                    _BaseFields = new List<string>( );
                    _BaseFields.AddRange( new string[ ] {
                        //"ContentTypeId",
                        "Title",
                        //"UniqueId"
                    }.ToList( ) );
                }
                return _BaseFields;
            }
            set
            {
                _BaseFields = value;
            }
        }

        ExportConfiguration currentConfiguration
        {
            get;
            set;
        }

        #endregion Properties

        #region Methods

        public bool FullExportToDocumentLibrary( ExportConfiguration configuration, out DateTime? currentExport )
        {
            currentConfiguration = configuration;
            setBaseFields( configuration );
            SPDocumentLibrary spDocumentLibrary = getDocumentLibrary( configuration.DocumentLibraryUrl );
            if ( spDocumentLibrary != null )
            {
                DateTime? _currentExport = null;
                string s = getListExport( resolveConfigurationHeaderSwitch( configuration ), configuration.ListUrl, null, out _currentExport );
                spDocumentLibrary.AddDocument( getStream( s ), configuration.DocumentName, string.Format( "Full export {0} as of {1}", configuration.ListUrl, DateTime.Now.ToLongDateTimeString( ) ) );
                currentExport = _currentExport;
                return true;
            }
            else
            {
                currentExport = null;
                return false;
            }
        }

        public bool IncrementalToDocumentLibrary( ExportConfiguration configuration, out DateTime? currentExport )
        {
            currentConfiguration = configuration;
            setBaseFields( configuration );
            SPDocumentLibrary spDocumentLibrary = getDocumentLibrary( configuration.DocumentLibraryUrl );
            if ( spDocumentLibrary != null )
            {
                DateTime? _currentExport = null;
                string s = getListExport( resolveConfigurationHeaderSwitch( configuration ), configuration.ListUrl, configuration.ResolvedLastExport, out _currentExport );
                spDocumentLibrary.AddDocument( getStream( s ), configuration.DocumentName, string.Format( "Incremental export {0} as of {1}", configuration.ListUrl, DateTime.Now.ToLongDateTimeString( ) ) );
                currentExport = _currentExport;
                return true;
            }
            else
            {
                currentExport = null;
                return false;
            }
        }

        internal string getRootWebUrl( )
        {
            using ( SPSite spSite = new SPSite( CurrentWebUrl ) )
            {
                return spSite.RootWeb.Url;
            }
        }

        internal void UpdateLastExport( DateTime? currentExport, ExportConfiguration config )
        {
            if ( currentExport.HasValue )
            {
                ExportConfigurationDao dao = string.IsNullOrEmpty( CurrentWebUrl ) ? new ExportConfigurationDao( ) : new ExportConfigurationDao( CurrentWebUrl );
                config.LastExport = currentExport;
                dao.Update( config );
            }
        }

        SPDocumentLibrary getDocumentLibrary( string documentLibraryUrl )
        {
            SharePointUri uri = new SharePointUri( documentLibraryUrl );
            if ( uri.ListFound )
                using ( SPSite spSite = new SPSite( documentLibraryUrl ) )
                {
                    using ( SPWeb spWeb = spSite.OpenWeb( ) )
                    {
                        return ( SPDocumentLibrary ) spWeb.Lists.GetListByRootFolder( uri.RootFolderName );
                    }
                }
            else
                return null;
        }

        List<FieldInfo> getFieldInfo( SPFieldCollection fields )
        {
            List<FieldInfo> l = new List<FieldInfo>( );
            foreach ( SPField f in fields )
            {
                if ( ( f.CanBeDeleted && ( !f.Hidden || ( f.Hidden && currentConfiguration.IncludeHidden.HasValue && currentConfiguration.IncludeHidden.Value ) ) ) ||
                    BaseFields.Contains( f.InternalName ) )
                {
                    l.Add( new FieldInfo
                       {
                           Hidden = f.Hidden,
                           CanBeDeleted = f.CanBeDeleted,
                           InternalName = f.InternalName,
                           Title = f.Title,
                           Type = f.Type,
                           ValueType = f.FieldValueType
                       } );
                }
            }
            return l;
        }

        string getHeaderRow( HeaderConstant header, List<FieldInfo> fields )
        {
            if ( header == HeaderConstant.None )
                return string.Empty;
            else
            {
                StringBuilder sb = new StringBuilder( );
                foreach ( FieldInfo f in fields )
                {
                    if ( header == HeaderConstant.InternalName )
                        sb.AppendFormat( "{0}{1}", f.InternalName, COLUMNDELIMITER );
                    else if ( header == HeaderConstant.Title )
                        sb.AppendFormat( "{0}{1}", f.Title, COLUMNDELIMITER );
                }

                if ( BaseFields.Contains( ITEMURL ) )
                    sb.AppendFormat( "{0}{1}", ITEMURL, COLUMNDELIMITER );
                string s = sb.ToString( ).TrimEnd( COLUMNDELIMITER );
                sb = null;
                return s;
            }
        }

        string getItemRow( List<FieldInfo> fields, SPListItem item )
        {
            StringBuilder sb = new StringBuilder( );
            foreach ( FieldInfo f in fields )
            {
                string value = string.Empty;
                object o = item[ f.InternalName ];
                if ( o != null )
                {
                    switch ( f.Type )
                    {
                        case SPFieldType.ContentTypeId:
                            {
                                // get and append name of associated content type
                                value = ( string ) item[ "ContentType" ];
                            }
                            break;
                        case SPFieldType.Counter:
                            {
                                value = item.GetValue<Int32>( f.InternalName ).ToString( );
                            }
                            break;
                        case SPFieldType.Integer:
                            {
                                value = item.GetValue<Int32>( f.InternalName ).ToString( );
                            }
                            break;
                        case SPFieldType.AllDayEvent:
                        case SPFieldType.Boolean:
                        case SPFieldType.Recurrence:
                            {
                                value = item.GetValue<bool>( f.InternalName ).ToString( );
                            }
                            break;
                        case SPFieldType.DateTime:
                            {
                                // append date as yyyy-MM-ddTHH:mm:ssZ
                                value = item.GetValue<DateTime>( f.InternalName ).ToShortXml( );
                            }
                            break;
                        case SPFieldType.Lookup:
                            {
                                if ( f.InternalName.Equals( "UniqueId" ) )
                                    value = o.ToString( );
                                else
                                {
                                    // append the lookup value
                                    SPLookupValue spLookupValue = item.GetValue<SPLookupValue>( f.InternalName );
                                    value = spLookupValue != null && spLookupValue.LookupValue != null ? spLookupValue.LookupValue : string.Empty;
                                }
                            }
                            break;
                        case SPFieldType.MultiChoice:
                            {
                                // append the values as a delimited string
                                List<string> list = item.GetValue<List<string>>( f.InternalName );
                                if ( list != null && list.Count > 0 )
                                {
                                    StringBuilder multiChoiceSb = new StringBuilder( );
                                    foreach ( string choice in list )
                                        multiChoiceSb.AppendFormat( "{0}{1}", choice, VALUEDELIMITER );
                                    value = multiChoiceSb.ToString( ).TrimEnd( VALUEDELIMITER );
                                    multiChoiceSb = null;
                                }
                            }
                            break;
                        case SPFieldType.Currency:
                            {
                                value = item.GetValue<double>( f.InternalName ).ToString( );
                            }
                            break;
                        case SPFieldType.Number:
                            {
                                value = item.GetValue<double>( f.InternalName ).ToString( );
                            }
                            break;
                        case SPFieldType.Calculated:
                            {
                                SPFieldCalculated p = ( SPFieldCalculated ) item.Fields.GetFieldByInternalName( f.InternalName );
                                value = p.GetFieldValueAsText( item[ f.InternalName ] );
                            }
                            break;
                        case SPFieldType.Computed:
                        case SPFieldType.Choice:
                        case SPFieldType.File:
                        case SPFieldType.Invalid:
                        case SPFieldType.Note:
                        case SPFieldType.Text:
                            {
                                if ( item[ f.InternalName ].GetType( ).UnderlyingSystemType == typeof( SPFieldMultiColumnValue ) )
                                    value = item[ f.InternalName ].ToString( );
                                else
                                    value = item.GetValue<string>( f.InternalName );
                            }
                            break;
                        case SPFieldType.URL:
                            {
                                // append the URL
                                SPFieldUrlValue spFieldUrlValue = item.GetValue<SPFieldUrlValue>( f.InternalName );
                                value = spFieldUrlValue == null ? string.Empty : spFieldUrlValue.Url;
                            }
                            break;
                        case SPFieldType.User:
                            {
                                // append the user name and login name as a delimited string
                                SPUserInfoExpanded spUserInfoExpanded = item.GetValue<SPUserInfoExpanded>( f.InternalName );
                                value = spUserInfoExpanded == null ? string.Empty : string.Format( "{0}{2}{1}", spUserInfoExpanded.Name, spUserInfoExpanded.LoginName, VALUEDELIMITER );
                            }
                            break;
                        default:
                            {
                                value = o.ToString( );
                            }
                            break;
                    }
                }
                if ( !string.IsNullOrEmpty( value ) )
                    value = value.Replace( Environment.NewLine, string.Empty );
                sb.AppendFormat( "{0}{1}", value, COLUMNDELIMITER );
            }

            // append item URL
            if ( BaseFields.Contains( ITEMURL ) )
                sb.AppendFormat( "{0}{1}", item.ItemUrl( ), COLUMNDELIMITER );

            string s = sb.ToString( );

            // remove last trailing column delimiter ONLY (NOTE: TrimEnd([column delimiter]) or variant Trim.. removes all trailing column delimiters)
            if ( !string.IsNullOrEmpty( s ) )
                s = s.Substring( 0, s.Length - 1 );
            sb = null;
            return s;
        }

        SPListItemCollection getItems( DateTime? lastExport, string rootFolderName, string siteUrl )
        {
            using ( SPSite spSite = new SPSite( siteUrl ) )
            {
                using ( SPWeb spWeb = spSite.OpenWeb( ) )
                {
                    SPList spList = spWeb.Lists[ rootFolderName ];
                    SPQuery spQuery = new SPQuery
                    {
                        Query = Query.GetQuery( lastExport )
                    };
                    return spList.GetItems( spQuery );
                }
            }
        }

        string getListExport( HeaderConstant header, string url, DateTime? lastExport, out DateTime? currentExport )
        {
            SharePointUri uri = new SharePointUri( url );
            // if list is found at specified location
            if ( uri.ListFound )
                using ( SPSite spSite = new SPSite( url ) )
                {
                    using ( SPWeb spWeb = spSite.OpenWeb( ) )
                    {
                        SPList spList = spWeb.Lists.GetListByRootFolder( uri.RootFolderName );
                        List<FieldInfo> fields = getFieldInfo( spList.Fields );

                        List<string> output = new List<string>( );

                        // add header row to output
                        if ( header != HeaderConstant.None )
                            output.Add( getHeaderRow( header, fields ) );

                        SPListItemCollection items = getItems( lastExport, uri.RootFolderName, uri.WebUrl );

                        DateTime? lastDate = null;
                        // add item value rows to output
                        foreach ( SPListItem item in items )
                        {
                            if ( !lastDate.HasValue )
                                lastDate = item.GetValue<DateTime>( MODIFIED );
                            else
                            {
                                DateTime modified = item.GetValue<DateTime>( MODIFIED );
                                if ( modified > lastDate.Value )
                                    lastDate = modified;
                            }
                            // add to output
                            output.Add( getItemRow( fields, item ) );
                        }

                        StringBuilder sb = new StringBuilder( );
                        foreach ( string o in output )
                            if ( !string.IsNullOrEmpty( o ) )
                                sb.AppendLine( o );
                        string s = sb.ToString( );
                        sb = null;

                        currentExport = lastDate;
                        return s;
                    }
                }
            else
            {
                currentExport = null;
                return string.Empty;
            }
        }

        Stream getStream( string listExport )
        {
            MemoryStream memoryStream = new MemoryStream( );
            StreamWriter streamWriter = new StreamWriter( memoryStream );
            streamWriter.WriteLine( listExport );
            streamWriter.Flush( );
            byte[ ] b = streamWriter.BaseStream.ToArray( );
            streamWriter.Close( );
            streamWriter.Dispose( );
            return new MemoryStream( b );
        }

        HeaderConstant resolveConfigurationHeaderSwitch( ExportConfiguration config )
        {
            switch ( config.Header.Value )
            {
                case Header.InternalName: // *InternalName
                    return HeaderConstant.InternalName;
                case Header.Title: // *Title
                    return HeaderConstant.Title;
                case Header.None:
                case Header.Invalid:
                case Header.None0: // *None
                default:
                    return HeaderConstant.None;
            }
        }

        void setBaseFields( ExportConfiguration configuration )
        {
            BaseFields = null;
            if ( configuration.IncludeContentType.HasValue && configuration.IncludeContentType.Value )
                BaseFields.Add( CONTENTTYPEID );
            if ( configuration.IncludeAuthor.HasValue && configuration.IncludeAuthor.Value )
                BaseFields.Add( AUTHOR );
            if ( configuration.IncludeCreated.HasValue && configuration.IncludeCreated.Value )
                BaseFields.Add( CREATED );
            if ( configuration.IncludeEditor.HasValue && configuration.IncludeEditor.Value )
                BaseFields.Add( EDITOR );
            if ( configuration.IncludeModified.HasValue && configuration.IncludeModified.Value )
                BaseFields.Add( MODIFIED );
            if ( configuration.IncludeItemUrl.HasValue && configuration.IncludeItemUrl.Value )
                BaseFields.Add( ITEMURL );
            if ( configuration.IncludeUniqueId.HasValue && configuration.IncludeUniqueId.Value )
                BaseFields.Add( UNIQUEID );
        }

        #endregion Methods
    }

    public class FieldInfo
    {
        #region Properties

        public bool CanBeDeleted
        {
            get;
            set;
        }

        public bool Hidden
        {
            get;
            set;
        }

        public string InternalName
        {
            get;
            set;
        }

        public string Title
        {
            get;
            set;
        }

        public SPFieldType Type
        {
            get;
            set;
        }

        public Type ValueType
        {
            get;
            set;
        }

        #endregion Properties

        #region Methods

        public override string ToString( )
        {
            return string.Concat( Title, " (", InternalName, ")" );
        }

        #endregion Methods
    }

    class Query
    {
        #region Fields

        public const string All = "<OrderBy><FieldRef Name='ID' Ascending='True'/></OrderBy>";
        public const string ByIncremental = "<Where><Gt><FieldRef Name='Modified' /><Value IncludeTimeValue='TRUE' Type='DateTime'>{0}</Value></Gt></Where><OrderBy><FieldRef Name='ID' Ascending='True' /></OrderBy>"; // {0} format :: 2012-09-24T06:41:39Z

        #endregion Fields

        #region Methods

        public static string Get( string query, params object[ ] args )
        {
            return string.Format( query, args );
        }

        public static string GetQuery( DateTime? datetime )
        {
            if ( datetime.HasValue )
                return string.Format( ByIncremental, datetime.Value.ToShortXml( ) );
            else
                return All;
        }

        #endregion Methods
    }
}